package com.mcintyret.site.validation.password;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * User: mcintyret2
 * Date: 07/04/2012
 */

@Component
public class PasswordValidator {

    private final static String DEFAULT_ERROR_MSG = "Invalid password";

    private final static Pattern NO_DIGITS = Pattern.compile("[^\\d]+");
    private final static Pattern NO_LETTERS = Pattern.compile("[^A-Za-z]+");

    @Value("${password.length.min}")
    private int minLength;
    @Value("${password.length.max}")
    private int maxLength;
    @Value("${password.forbidden.chars}")
    private String forbiddenChars;

    public void validatePassword(String password, BindingResult errors) {

        if (StringUtils.isBlank(password)) {
            errors.rejectValue("password", PasswordValidationErrorCodes.NULL.getErrorCode(), DEFAULT_ERROR_MSG);
        }
        if (password.length() < minLength) {
            errors.rejectValue("password", PasswordValidationErrorCodes.TOO_SHORT.getErrorCode(), arrayOf(minLength),
                               DEFAULT_ERROR_MSG);
        }
        if (password.length() > maxLength) {
            errors.rejectValue("password", PasswordValidationErrorCodes.TOO_LONG.getErrorCode(), arrayOf(maxLength),
                               DEFAULT_ERROR_MSG);
        }

        if (NO_LETTERS.matcher(password).matches()) {
            // password has no letters at all!
            errors.rejectValue("password", PasswordValidationErrorCodes.NO_LETTERS.getErrorCode(), DEFAULT_ERROR_MSG);
        } else {
            String passwordLetters = password.replaceAll("[^A-Za-z]", "");
            if (StringUtils.isAllLowerCase(passwordLetters)) {
                errors.rejectValue("password", PasswordValidationErrorCodes.ALL_LOWER_CASE.getErrorCode());
            } else if (StringUtils.isAllUpperCase(passwordLetters)) {
                errors.rejectValue("password", PasswordValidationErrorCodes.ALL_UPPER_CASE.getErrorCode());
            }
        }

        if (NO_DIGITS.matcher(password).matches()) {
            errors.rejectValue("password", PasswordValidationErrorCodes.NO_DIGITS.getErrorCode(), DEFAULT_ERROR_MSG);
        }

        int illegalIndex = StringUtils.indexOfAny(password, forbiddenChars);
        if (illegalIndex != StringUtils.INDEX_NOT_FOUND) {
            errors.rejectValue("password", PasswordValidationErrorCodes.FORBIDDEN_CHAR.getErrorCode(),
                               arrayOf(findForbiddenChars(password), expandForbiddenChars()), DEFAULT_ERROR_MSG);
        }


    }

    private String expandForbiddenChars() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < forbiddenChars.length(); i++) {
            sb.append(forbiddenChars.charAt(i));
            if (i != forbiddenChars.length() - 1) {
                sb.append(", ");
            }
        }
        return sb.toString();
    }

    @VisibleForTesting
    void setMinLength(int minLength) {
        this.minLength = minLength;
    }

    @VisibleForTesting
    void setMaxLength(int maxLength) {
        this.maxLength = maxLength;
    }

    @VisibleForTesting
    void setForbiddenChars(String forbiddenChars) {
        this.forbiddenChars = forbiddenChars;
    }

    private Object[] arrayOf(Object... obj) {
        return obj;
    }

    private String findForbiddenChars(String password) {
        List<Character> forbidden = new LinkedList<Character>();
        for (char c : forbiddenChars.toCharArray()) {
            if (password.indexOf(c) >= 0) {
                forbidden.add(c);
            }
        }
        return StringUtils.join(forbidden, ", ");
    }

}
